#include "mmu.h"
#include "mmu_asm.h"
#include "mem_struct.h"

extern unsigned long __end;
extern unsigned long start;

#define _16K (1<<14)
#define _1MB (1<<20)

#define round(val, x) (((val + (x-1)) / x) * x)

#define RAM_START (0x20000000)
#define RAM_SIZE  (64*_1MB)
#define RAM_END   (RAM_START + RAM_SIZE)

#define KERNEL_START ((unsigned long)&start)
#define KERNEL_END ((unsigned long)&__end)

/* Anzahl der 1MB Einträge in einer L1-Tabelle */
#define L1_NUM_ENTRIES 4096

/* Adresse der L1-Tabelle im Speicher
 * WICHTIG: Muss an einer 16K-Adresse ausgreichtet sein */
//static unsigned int* L1_TABLE = (unsigned int*)0x20000000;
static unsigned int *L1_TABLE;

static mem_struct_t free_pages;

/* Bitmasken, die zur Ausblendung nicht benötigter Bits für make_section dienen */
#define SECTION_ADDRESS_MASK        0xfff00000
#define DOMAIN_MASK                 (16 - 1)
#define PERMISSION_MASK             ((1 << 1) | (1 << 0))
#define CACHE_CONFIG_MASK           ((1 << 1) | (1 << 0))

/* Bitmaske, die den korrekten Typ eines L1-Eintrags in make_section setzt
 * 0x12 == 1 00 10
 *         ^    ^
 *         |    |--- 10b == 2d == MASTER aus page_table_type_t
 *         |
 *         |--- Muss laut ARM-Datenblatt 1 sein! */
#define SECTION_MASK                (MASTER + (1 << 4))


/**
 * make_section baut aus den übergebenen Parametern einen 32bit Wert zusammen,
 * der direkt in die L1-Tabelle eingetragen werden kann.
 *
 * 31 .............  20 | 19 ... 12 | 11       10 |  9  | 8 ... 5 |  4  | 3     |  2       | 1 0
 * Section base address |    SBZ    | Permissions | SBZ | Domain  |  1  | Cache | Buffered | 1 0
 *
 * SBZ = Should Be Zero
 */
static unsigned long
make_section(unsigned long section_base_addr,
        unsigned char permission,
        unsigned char domain,
        unsigned char cache_config)
{

        /* Ausblenden nicht benötigter Bits */
	section_base_addr   &= SECTION_ADDRESS_MASK;
	permission          &= PERMISSION_MASK;
	domain              &= DOMAIN_MASK;
	cache_config        &= CACHE_CONFIG_MASK;

        /* Endergebnis durch Ver-ODER-ung erstellen */
	return section_base_addr | (permission << 10) | (domain << 5) |
                (cache_config << 2) | SECTION_MASK;
}

void
mmu_map_memory(unsigned long physicalAddress,
        unsigned long number_of_1mb_blocks,
        unsigned long virtualAddress,
        region_access_permission_t access_permission,
        page_table_domain_t domain)
{
    unsigned int i;

    /* L1-Eintrag mittels make_section zusammenbauen */
    unsigned long l1_entry =
        make_section(physicalAddress,
            access_permission,
            domain,
            NOTCACHED_NOTBUFFERED);
    
    /* Adresse auf Eintrag in L1-Tabelle holen, der zu virtualAddress gehört */
    unsigned int *table_ptr = L1_TABLE + (virtualAddress >> 20);

    /* So viele Einträge manipulieren, wie number_of_1mb_blocks vorgibt */
    for(i=0;i<number_of_1mb_blocks;i++) {
        *table_ptr = l1_entry;

        /* Zeiger auf nächsen Eintrag verschieben */
        table_ptr++;

        /* section base address im nächsten L1-Eintrag um 1MB erhöhen */
        l1_entry+=1<<20;
    }
    
}

void *
mmu_alloc_pages(int n)
{
    return mem_alloc(&free_pages, n * _1MB);
}

void 
mmu_free_pages(void *page, int n)
{
    mem_free(&free_pages, page, n * _1MB);
}

int
init_mmu()
{
    /* L1-Tabelle mit FAULT-Werten initialisieren */
    int i = 0;

    //starte L1 Tabelle nach Kernelende und align bei 16k
    L1_TABLE = (unsigned int*)round(KERNEL_END, _16K);
    unsigned long new_end = round(((unsigned long)L1_TABLE + L1_NUM_ENTRIES*4), _1MB);

    //verwalte verfuegbaren speicher fuer pages
    mem_init(&free_pages, (void*)RAM_START, KERNEL_START - RAM_START);
    mem_free(&free_pages, (void*)new_end, RAM_END - new_end);

    for (; i < L1_NUM_ENTRIES; i++)
        L1_TABLE[i] = FAULT;

    /* L1-Tabelle der CPU bekanntgeben, indem die Adresse in das TTB Register von
     * Coprozessor 15 geschreiben wird */
    write_cp15_ttb_reg(L1_TABLE);

    //mapping interner speicher (nur priviligierter Modus)
    mmu_map_memory(0x00000000,5,    0x00000000,RWNA,CLIENT);

    //externer Flash (nur priviligierter Modus)
    mmu_map_memory(0x10000000,256,  0x10000000,RWNA,CLIENT);

    //externer RAM (alle prozesse)
    mmu_map_memory(0x20000000,512,  0x20000000,RWRW,MANAGER);

    //LCD data bus mapping (nur priviligierter Modus)
    mmu_map_memory(0x40000000,1,    0x40000000,RWNA,CLIENT);

    //interne peripherie (nur priviligierter Modus)
    mmu_map_memory(0xF0000000,256,  0xF0000000,RWNA,CLIENT);

    
    /* Setze Zugriffsberechtigungen für alle Domänen */
    write_cp15_domain_reg((MANAGER << 6) | (CLIENT << 2)) ;

    /* Einschalten der MMU */
    unsigned long cp15reg = read_cp15_control_reg();
    cp15reg |= 0x00000001;
    write_cp15_control_reg(cp15reg);

    return 1;
}

